#include "ph_hashmap.h"

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "xlog.h"

typedef struct pair_t
{
	void *	k;
	size_t	kl;
	void *	v;
	size_t	vl;

	struct pair_t * n;
} pair_t;

typedef struct node_t
{
	hash_t		h;

	pair_t *	l;
} node_t;

enum
{
	HM_CAPA_K_MIN	= 4,
	HM_CAPA_K_MAX	= 20,
};

struct ph_hashmap_t
{
	int	flags;

	hash_f	hf;

	int (*set_k)(pair_t *p, void *k, size_t kl);
	int (*set_v)(pair_t *p, void *v, size_t vl);
	int (*del_k)(pair_t *p);
	int (*del_v)(pair_t *p);
	int (*replace)(pair_t *p, void *v, size_t vl);

	uint8_t		capa_k;
	uint32_t	capa;
	uint32_t	mask;
	uint32_t    tolerate;
	uint32_t	size;

	node_t * nl;
};

static int
set_k_copy(pair_t *p, void *k, size_t kl)
{
	p->k = malloc(kl);
	if (!p->k) {
		return HM_NOMEM;
	}

	memcpy(p->k, k, kl);
	p->kl = kl;
	return HM_OK;
}

static int
set_k_nocopy(pair_t *p, void *k, size_t kl)
{
	p->k = k;
	p->kl = kl;
	return HM_OK;
}

static int
set_v_copy(pair_t *p, void *v, size_t vl)
{
	p->v = malloc(vl);
	if (!p->v) {
		return HM_NOMEM;
	}

	memcpy(p->v, v, vl);
	p->vl = vl;
	return HM_OK;
}

static int
set_v_nocopy(pair_t *p, void *v, size_t vl)
{
	p->v = v;
	p->vl = vl;
	return HM_OK;
}

static int
del_k_copy(pair_t *p)
{
	free(p->k);
	return HM_OK;
}

static int
del_v_copy(pair_t *p)
{
	free(p->v);
	return HM_OK;
}

static int
del_nocopy(pair_t *p)
{
	return HM_OK;
}

static int
replace_copy(pair_t *p, void *v, size_t vl)
{
	void * new_v = malloc(vl);
	if (!new_v) {
		return HM_NOMEM;
	}

	memcpy(new_v, v, vl);

	free(p->v);
	p->v = new_v;
	p->vl = vl;

	return HM_OK;
}

static int
replace_nocopy(pair_t *p, void *v, size_t vl)
{
	p->v = v;
	p->vl = vl;
	return HM_OK;
}

static inline uint32_t
gen_tolerate(uint32_t capa)
{
    return capa * 0.8;
}

static int hm_scale(hm_t *hm);

hm_t *
hm_create0(int capa_k, int flags, hash_f hf)
{
	hm_t * hm = calloc(sizeof(hm_t), 1);
	if (hm) {
		hm->flags = flags;

		if (flags & HM_K_COPY) {
			hm->set_k = set_k_copy;
			hm->del_k = del_k_copy;
		}
		else {
			hm->set_k = set_k_nocopy;
			hm->del_k = del_nocopy;
		}

		if (flags & HM_V_COPY) {
			hm->set_v = set_v_copy;
			hm->del_v = del_v_copy;
			hm->replace = replace_copy;
		}
		else {
			hm->set_v = set_v_nocopy;
			hm->del_v = del_nocopy;
			hm->replace = replace_nocopy;
		}

		hm->hf = hf;
		if (!hm->hf) {
			hm->hf = simple_hash;
		}

		hm->capa_k = capa_k;
		if (hm->capa_k < HM_CAPA_K_MIN) {
			hm->capa_k = HM_CAPA_K_MIN;
		}
		else if(hm->capa_k > HM_CAPA_K_MAX) {
			hm->capa_k = HM_CAPA_K_MAX;
		}
		hm->capa = 1 << hm->capa_k;
		hm->mask = hm->capa - 1;
		hm->tolerate = gen_tolerate(hm->capa);

		hm->nl = calloc(sizeof(node_t), hm->capa);

		if (hm->nl) {
			int i = 0;
			for (i = 0; i < hm->capa; ++i) {
				hm->nl[i].h = i;
			}
		}
		else {
			free(hm);
			return 0;
		}
	}

	return hm;
}

int
hm_release(hm_t *hm)
{
	int rc = HM_OK;

	// TODO
	(void)hm;

	return rc;
}

size_t
hm_size(hm_t * hm)
{
	return hm->size;
}

int
hm_put(hm_t *hm, void *k, size_t kl, void *v, size_t vl)
{
	int rc = HM_OK;

	hm_scale(hm);

	hash_t h = hm->hf(k, kl) & hm->mask;

	node_t * n = hm->nl + h;

	pair_t ** p = &n->l;

	for (; *p; p = &(*p)->n) {
		if ((*p)->kl == kl && !memcmp((*p)->k, k, kl)) {
			if ((rc = hm->replace(*p, v, vl))) {
				return rc;
			}
		}
	}

	if (!*p) {
		pair_t *new_p = calloc(1, sizeof(*new_p));
		if (!new_p) {
			return HM_NOMEM;
		}

		if ((rc = hm->set_k(new_p, k, kl))) {
			free(new_p);
			return rc;
		}
		if ((rc = hm->set_v(new_p, v, vl))) {
			hm->del_k(*p);
			free(new_p);
			return rc;
		}

		*p = new_p;

		++hm->size;
	}

	return rc;
}

int
hm_get(hm_t *hm, void *k, size_t kl, void **d, size_t *dl)
{
	int rc = HM_NOT_FOUND;

	hash_t h = hm->hf(k, kl) & hm->mask;

	node_t * n = hm->nl + h;

	pair_t * p = n->l;

	for (; p; p = p->n) {
		if (p->kl == kl && !memcmp(p->k, k, kl)) {
			if (d) { *d = p->v; }
			if (dl) { *dl = p->vl; }
			rc = HM_OK;
			break;
		}
	}

	return rc;
}

int
hm_del(hm_t *hm, void *k, size_t kl, void **v, size_t *vl)
{
	int rc = HM_NOT_FOUND;

	hash_t h = hm->hf(k, kl) & hm->mask;

	node_t * n = hm->nl + h;

	pair_t ** p = &n->l;

	for (; *p; p = &(*p)->n) {
		if ((*p)->kl == kl && !memcmp((*p)->k, k, kl)) {
			pair_t * pair = *p;
			*p = pair->n;
			hm->del_k(pair);
			if (v) {
				*v = pair->v;
			}
			else {
				hm->del_v(pair);
			}
			if (vl) {
				*vl = pair->vl;
			}
			free(pair);

			--hm->size;

			return HM_OK;
		}
	}

	return rc;
}

int
hm_foreach(hm_t * hm, int(*f)(void * ctx, void *k, size_t kl, void *d, size_t dl), void *ctx)
{
	int rc = HM_OK;

	int i = 0;
	node_t * n = hm->nl;
	pair_t * p = 0;
	for (; i < hm->capa; ++i, ++n) {
		for (p = n->l; p; p = p->n) {
			if (f(ctx, p->k, p->kl, p->v, p->vl)) {
				return HM_ERR;
			}
		}
	}

	return rc;
}

static int
hm_scale(hm_t *hm)
{
    if (hm->size < hm->tolerate) {
        return HM_OK;
    }

    if (hm->capa_k == HM_CAPA_K_MAX) {
        return HM_CAPA_LIMIT;
    }

    uint32_t capa = hm->capa << 1;
    uint32_t mask = capa - 1;
    node_t * nl = calloc(capa, sizeof(*nl));
    if (!nl) {
        return HM_NOMEM;
    }

    int i = 0;
    for (; i < capa; ++i) {
        nl[i].h = i;
    }

    node_t * n = hm->nl;
    pair_t * p = 0;
    pair_t * np = 0;
    hash_t h = 0;
    for (i = 0; i < hm->capa; ++i, ++n) {
        for (p = n->l; p; p = np) {
            np = p->n;
            h = hm->hf(p->k, p->kl) & mask;
            p->n = nl[h].l;
            nl[h].l = p;
        }
    }

    free(hm->nl);
    hm->nl = nl;
    ++hm->capa_k;
    hm->capa = capa;
    hm->mask = mask;
    hm->tolerate = gen_tolerate(capa);

    return HM_OK;
}

void
hm_print(hm_t *hm)
{
	xlog("capa_k : %d", hm->capa_k);
	xlog("capa   : %d", hm->capa);
	xlog("size   : %d", hm->size);

	int i = 0;
	node_t * n = 0;
	pair_t * p = 0;
	for (i = 0, n = hm->nl; i < hm->capa; ++i, ++n) {
	    p = n->l;
	    if (p) {
	        xlog(" -- %d", i);
	    }
	    for (; p; p = p->n) {
	        xlog("\t\t k:%s \t--\tv:%s", (const char *)p->k, (const char *)p->v);
	    }
	}
}


